package webserver;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.InputStreamReader;

import webserver.request.Request;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import webserver.configuration.ConfigVars;
import webserver.configuration.MIME;
import webserver.configuration.HttpdConf;
import webserver.request.HttpReqMsg;
import webserver.response.*;

public class WebServer {

	private ServerSocket serverSocket;
	private Socket socket = null;
	private static int tID = 0;
	private static Thread[] thread;

	public WebServer() {
		// Parsing httpd.conf and mime.types files as the web server starts
		String userDir = System.getProperty("user.dir");
		String sep = System.getProperty("file.separator");

		System.out.println("Configuring the web server ..." + "\n");
		new HttpdConf(userDir + sep + "conf" + sep + "httpd.conf")
				.processConfigFile();
		new MIME(userDir + sep + "conf" + sep + "mime.types").processMIMEFile();
	}

	public void webServerReady() throws InterruptedException {
		int maxThread = Integer.parseInt(ConfigVars.getMaxThread());
		thread = new Thread[maxThread];
		try {
			serverSocket = new ServerSocket(Integer.parseInt(ConfigVars
					.getListen()));
			System.out.println("Socket Opened on Port "
					+ ConfigVars.getListen() + ".");
		} catch (IOException e) {
			System.out.println("Failed to open socket on port "
					+ ConfigVars.getListen() + ".");
			System.exit(1);
		}
		while (true) {
			try {
				socket = serverSocket.accept();
				tID++;
				thread[tID] = new Thread(new ReqThread(socket, tID));
				thread[tID].start();

			} catch (IOException e) {
				System.out.println("Accept Failed on: "
						+ ConfigVars.getListen());
				System.exit(1);
			}

		}
	}

	public static void main(String[] args) throws IOException,
			InterruptedException {
		WebServer ws = new WebServer();
		ws.webServerReady();

	}
}

class ReqThread implements Runnable {

	private Socket socket;
	private Request request;
	private Response response;
	private HttpReqMsg reqMsg;
	private HttpRespMsg respMsg;
	private FileWriter logFile;
	private BufferedReader req;
	BufferedOutputStream out;
	private int tID;

	public ReqThread(Socket clientSocket, int id) {
		this.socket = clientSocket;
		this.tID = id;
		try {
			req = new BufferedReader(new InputStreamReader(
					socket.getInputStream()));
			out = new BufferedOutputStream(socket.getOutputStream());
		} catch (IOException e) {
			System.out.println(e);
		}
	}

	synchronized public void writeLog() throws IOException {
		char quotation = '"';
		long currentTime = System.currentTimeMillis();
		SimpleDateFormat sdf = new SimpleDateFormat(
				"EEE, dd MMM yyyy HH:mm:ss z");

		logFile = new FileWriter(ConfigVars.getLogFile(), true);
		PrintWriter lwrite = new PrintWriter(logFile, true);
		lwrite.print(socket.getLocalAddress().getHostAddress() + " -"
				+ socket.getLocalAddress().getCanonicalHostName() + " "
				+ quotation + reqMsg.getMethodName() + " " + reqMsg.getURI()
				+ " " + respMsg.getVersion() + quotation + " "
				+ respMsg.getStatusCode() + " " + respMsg.getDescription()
				+ " ");
		if (respMsg.getBody() != null) {
			lwrite.print(Long.toString(respMsg.getBody().length()) + " Bytes");
		} else {
			lwrite.print("0 Bytes");
		}
		lwrite.println(" " +sdf.format(new Date(currentTime)));
	}

	public void run() {
		request = new Request(req);
		reqMsg = request.parseRequest();
		response = new Response(reqMsg, socket);
		respMsg = response.buildResponse();
		
		if (respMsg != null) {
			sendResponse();
		}
		try {
			writeLog();
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
	}

	public void sendResponse() {
		try {
			BufferedOutputStream out = new BufferedOutputStream(
					socket.getOutputStream());
			PrintWriter writer = new PrintWriter(out, true);

			// writes request's first line
			writer.println(respMsg.getVersion() + " " + respMsg.getStatusCode()
					+ " " + respMsg.getDescription());
			System.out.println(respMsg.getVersion() + " "
					+ respMsg.getStatusCode() + " " + respMsg.getDescription());
			// writes the headers
			Iterator<Entry<String, String>> it = respMsg.getHeaders()
					.entrySet().iterator();
			while (it.hasNext()) {
				Map.Entry<String, String> pairs = (Map.Entry<String, String>) it
						.next();
				writer.println(pairs.getKey() + ": " + pairs.getValue());
				it.remove();
			}
			if (!reqMsg.getCgiScript()) {
				writer.println("");
			}
			// write file
			if (respMsg.getBody() != null) {
				byte[] body = new byte[(int) respMsg.getBody().length()];
				FileInputStream in = new FileInputStream(respMsg.getBody());
				BufferedInputStream bufferedIn = new BufferedInputStream(in);
				bufferedIn.read(body, 0, body.length);
				// OutputStream os = sock.getOutputStream();
				System.out.println(tID + " is sending..." + "\n");
				out.write(body, 0, body.length);
				out.flush();

			}
			out.close();
			socket.close();
		} catch (IOException ex) {
			System.out.println("Access Failed: " + ConfigVars.getListen()
					+ ex.getMessage());
		}
	}

}
